#ifndef SIMPLE_IO_EXECUTE_H
#define SIMPLE_IO_EXECUTE_H

#include <cstddef> // std::size_t
#include <array> // std::array
#include <tuple> // std::tuple std::tuple_size_v
#include <variant> // std::variant std::visit
#include <type_traits> // std::enable_if_t std::true_type std::false_type std::is_convertible_v
#include <string> // std::string
#include <utility> // std::move std::forward

#include "simple/support/meta/list.hpp" // support::meta::list support::meta::list_size_v
#include "simple/support/meta/transform.hpp" // support::meta::transform_t
#include "simple/support/meta/pend.hpp" // support::meta::prepend_t support::meta::concat_t
#include "simple/support/meta/find.hpp" // support::meta::find_v
#include "simple/support/function_utils.hpp" // support::overload
#include "simple/support/tuple_utils/transform.hpp" // support::transform support::tuple_void[_t]

#include "error.h" // error
#include "handle_common.h" // operation custom_unique_ptr
#include "handle.h" // interface
#include "standard.h" // standard_input_t standard_output_t standard_error_t all_standard_handles
#include "open_execute_common.h" // *

namespace simple::io
{
	namespace meta = support::meta;

	enum class process_id : long {};

	struct wait_process_config
	{
		using handle_t = process_id;
		static process_id null() noexcept;
		static void deleter(process_id) noexcept;
	};

	using process = custom_unique_ptr<wait_process_config>;

	struct process_limit_reached : error {};
	struct arguments_size_limit_reached : error {};
	struct executable_size_limit_reached : error {};
	struct executable_format_not_supported : error {};
	struct file_is_being_written : error {};

	using execute_error_t = std::variant<
		access_denied,
		symbolic_link_loop,
		path_component_length_exceeded,
		no_such_entity,
		no_such_directory,
		file_is_being_written,
		process_descriptor_limit_reached,
		system_descriptor_limit_reached,
		process_limit_reached,
		arguments_size_limit_reached,
		executable_size_limit_reached,
		executable_format_not_supported,
		error
	>;

	using execute_success_t = std::tuple<
		process, unique_handle, unique_handle, unique_handle>;

	using execute_result = meta::prepend_t<execute_error_t, execute_success_t>;

	constexpr auto exec_capture_max = std::tuple_size_v<all_standard_handles_t>;

	execute_result execute(std::array<int,exec_capture_max> capture, const char* file, const char** args) noexcept;

	template <typename T>
	constexpr bool is_standard_handle_v = meta::find_v<T, all_standard_handles_t> !=
		meta::list_size_v<all_standard_handles_t>;

	template <typename T>
	struct interface_for_standard_redirect;
	template <> struct interface_for_standard_redirect<standard_input_t>
	{
		static constexpr operation value = operation::write;
		using type = interface<operation::write>;
	};
	template <> struct interface_for_standard_redirect<standard_output_t>
	{
		static constexpr operation value = operation::read;
		using type = interface<operation::read>;
	};
	template <> struct interface_for_standard_redirect<standard_error_t>
	{
		static constexpr operation value = operation::read;
		using type = interface<operation::read>;
	};
	template <typename T>
	using interface_for_standard_redirect_t = typename interface_for_standard_redirect<T>::type;
	template <typename T>
	constexpr auto interface_for_standard_redirect_v = interface_for_standard_redirect<T>::value;

	template <typename T, std::enable_if_t<is_standard_handle_v<T>>* = nullptr>
	interface_for_standard_redirect_t<T> decorate_redirect_handle(unique_handle h, T)
	{ return {std::move(h)}; }

	// TODO: make this work with any type list and move to support
	template <typename List>
	struct has_duplicates;
	template <> struct has_duplicates<meta::list<>> : std::false_type {};
	template <typename First, typename... Rest> struct has_duplicates<meta::list<First, Rest...>>
	{
		constexpr static bool value = meta::find_v<First, meta::list<Rest...>> != sizeof...(Rest) ||
			has_duplicates<meta::list<Rest...>>::value;
	};
	template <typename List>
	constexpr auto has_duplicates_v = has_duplicates<List>::value;

	template <typename... Capture,
		std::enable_if_t<(is_standard_handle_v<Capture> && ...)>* = nullptr,
		std::enable_if_t<not has_duplicates_v<meta::list<Capture...>>>* = nullptr
	>
	auto execute(const char* file, const char** args) noexcept
	{
		std::array<int,exec_capture_max> capture_fds{Capture::value...};
		for(std::size_t i = sizeof...(Capture); i < capture_fds.size(); ++i)
			capture_fds[i] = -1;
		using success_t = meta::concat_t<std::tuple<process>,
			meta::transform_t<meta::list<Capture...>, interface_for_standard_redirect_t>>;
		using result_t = meta::prepend_t<execute_error_t, success_t>;
		return std::visit(support::overload{
			[](execute_success_t&& s) -> result_t {
				return support::transform(
					support::overload{
						[](support::tuple_void_t, process&& p) { return std::move(p); },
						[](auto redir, unique_handle&& h)
							{ return decorate_redirect_handle(std::move(h), redir); }
					},
					std::tuple{support::tuple_void, Capture{}...}, std::move(s)
				);
			},
			[](auto&& x) -> result_t { return std::move(x); }},
		execute(capture_fds, file, args));
	};

	template <typename... Capture,
		std::enable_if_t<(is_standard_handle_v<Capture> && ...)>* = nullptr,
		std::enable_if_t<not has_duplicates_v<meta::list<Capture...>>>* = nullptr
	>
	auto execute(meta::list<const Capture&...>, const char* file, const char** args) noexcept
	{ return execute<Capture...>(file, args); }

	namespace detail
	{

		inline const char* get_c_str(const std::string& s) { return s.c_str(); }
		inline const char* get_c_str(const char* s) { return s; }

	} // namespace detail

	template <typename... Capture, typename... Args,
		std::enable_if_t<(is_standard_handle_v<Capture> && ...)>* = nullptr,
		std::enable_if_t<not has_duplicates_v<meta::list<Capture...>>>* = nullptr,
		std::enable_if_t<((std::is_convertible_v<Args,const char*> || std::is_same_v<Args, std::string>) && ...)>* = nullptr
	>
	auto execute(meta::list<const Capture&...> captures, const std::string& file, const Args& ... args) noexcept
	{
		std::array<const char*, sizeof...(Args) + 1> arg_array {(detail::get_c_str(args))..., 0};
		return execute(captures, file.c_str(), arg_array.data());
	}

	template <typename... Capture, typename... Args,
		std::enable_if_t<(is_standard_handle_v<Capture> && ...)>* = nullptr,
		std::enable_if_t<not has_duplicates_v<meta::list<Capture...>>>* = nullptr,
		std::enable_if_t<((std::is_convertible_v<Args,const char*> || std::is_same_v<Args, std::string>) && ...)>* = nullptr
	>
	auto execute(const std::string& file, const Args& ... args) noexcept
	{
		std::array<const char*, sizeof...(Args) + 1> arg_array {(detail::get_c_str(args))..., 0};
		return execute<Capture...>(file.c_str(), arg_array.data());
	}

	constexpr struct execute_f_t
	{
		template <typename... Args>
		[[nodiscard]] decltype(auto) operator()(Args&& ... args) const noexcept
		{ return simple::io::execute(std::forward<Args>(args)...); }
	} execute_f;

	[[nodiscard]] inline std::string get_message(const execute_f_t&)
	{ return "simple::io::execute"; }

} // namespace simple::io

#endif /* end of include guard */
